package com.jiyici.controller;

import java.util.ArrayList;
import java.util.Iterator;

import android.content.Context;
import android.content.Intent;

import com.jiyici.R;
import com.jiyici.data.ClientDBHelper;
import com.jiyici.model.Category;
import com.jiyici.model.Item;
import com.jiyici.model.ItemParam;
import com.jiyici.model.Record;
import com.jiyici.model.RecordParam;
import com.jiyici.model.User;
import com.jiyici.utils.Global;
import com.jiyici.utils.LogUtil;
import com.jiyici.utils.SortOrder;
import com.jiyici.utils.Utils;
import com.jiyici.view.DialogUtil.Notify;

public class DataManager {
    
    private static final String TAG = "data-manager";

    public static final String ACTION_DATA_CHANGED = "com.jiyici.ACTION_DATA_CHANGED";
    public static final String ACTION_START_LOAD_DATA = "com.jiyici.ACTION_START_LOADING_DATA";
    public static final String ACTION_LOAD_DATA_FAILED = "com.jiyici.ACTION_LOAD_DATA_FAILED";
    public static final String EXTRA_KEY_DATA_TYPE = "data_type";
    public static final int DATA_CHANGED_CATEGORY_ITEM = 1;
    public static final int DATA_CHANGED_RECORD = 2;
    
    public static interface DataListener {
        void onItemCatgoryChanged();
        void onRecordChanged();
    }

    private static final String PREF_NAME = "jiyici-data";

    private static final String KEY_USERID = "com.jiyici.KEY_USERID";
    private static final String KEY_EMAIL = "com.jiyici.KEY_EMAIL";
    private static final String KEY_USERNAME = "com.jiyici.KEY_USERNAME";
    private static final String KEY_ACCESS_TOKEN = "com.jiyici.KEY_ACCESS_TOKEN";
    private static final String KEY_SHOW_NOTIFY_DIALOG = "com.jiyici.KEY_SHOW_NOTIFY_DIALOG";

    private static User CURRENT_USER = null;

    private static ArrayList<Category> mCategoryList;

    /**
     * a list which mixing category and item. eg:
     * [c0],[i0],[i1],..,[in],[c1][in+1],...
     */
    private static ArrayList<Object> mDatas;

    public static void saveUser(User user) {
        Context context = Global.APP_CONTEXT;
        Utils.setPreferenceLongData(context, PREF_NAME, KEY_USERID, user.id);
        Utils.setPreferenceStringData(context, PREF_NAME, KEY_EMAIL, user.email);
        Utils.setPreferenceStringData(context, PREF_NAME, KEY_USERNAME, user.userName);
        Utils.setPreferenceStringData(context, PREF_NAME, KEY_ACCESS_TOKEN, user.token);
        CURRENT_USER = user;
    }

    public static User getUser() {
        if (CURRENT_USER != null)
            return CURRENT_USER;
        User user = new User();
        Context context = Global.APP_CONTEXT;
        long id = Utils.getPreferenceData(context, PREF_NAME, KEY_USERID, 0L);
        user.id = id;
        user.email = Utils.getPreferenceData(context, PREF_NAME, KEY_EMAIL, "");
        user.userName = Utils.getPreferenceData(context, PREF_NAME, KEY_USERNAME, "");
        user.token = Utils.getPreferenceData(context, PREF_NAME, KEY_ACCESS_TOKEN, "");
        CURRENT_USER = user;
        return user;
    }

    public static void signoff() {
        Context context = Global.APP_CONTEXT;
        Utils.setPreferenceLongData(context, PREF_NAME, KEY_USERID, 0L);
        Utils.setPreferenceStringData(context, PREF_NAME, KEY_EMAIL, "");
        Utils.setPreferenceStringData(context, PREF_NAME, KEY_USERNAME, "");
        Utils.setPreferenceStringData(context, PREF_NAME, KEY_ACCESS_TOKEN, "");
        CURRENT_USER = null;
    }
    
    public static void setShowNotifyDialog(Notify notify, boolean show) {
        Context context = Global.APP_CONTEXT;
        Utils.setPreferenceBooleanData(context, PREF_NAME, KEY_SHOW_NOTIFY_DIALOG + "." + notify.getNotifyType(), show);
    }
    
    public static boolean getShowNotifyDialog(Notify notify) {
        Context context = Global.APP_CONTEXT;
        return Utils.getPreferenceData(context, PREF_NAME, KEY_SHOW_NOTIFY_DIALOG + "." + notify.getNotifyType(), true);
    }

    public static void startLoadData() {
        Thread thread = new Thread(new LoadItemTask());
        thread.start();
    }

    private static void setDatas(ArrayList<Category> categories, ArrayList<Item> itemList) {
        mCategoryList = categories;

        /* Organize data */
        ArrayList<Object> dataList = new ArrayList<Object>();
        int categorySize = categories.size();
        for (int i = 0; i < categorySize; i++) {
            Category category = categories.get(i);
            dataList.add(category);
            for (Iterator<Item> itemIterator = itemList.iterator(); itemIterator.hasNext();) {
                Item item = itemIterator.next();
                if (category.getId() == item.categoryId) {
                    dataList.add(item);
                    itemIterator.remove();
                }
            }
        }
//        if (itemList.size() > 0) {
            ArrayList<Object> nocateList = new ArrayList<Object>();
            Category noCate = new Category();
            noCate.name = Global.APP_CONTEXT.getResources().getString(R.string.category_none);
            nocateList.add(noCate);
            for (Iterator<Item> itemIterator = itemList.iterator(); itemIterator.hasNext();) {
                Item item = itemIterator.next();
                nocateList.add(item);
            }
            dataList.addAll(0, nocateList);
//        }

        mDatas = dataList;

        sendBroadcastForDataChanged(DATA_CHANGED_CATEGORY_ITEM);
    }

    public static ArrayList<Object> getDataList() {
        if (mDatas == null)
            mDatas = new ArrayList<Object>();
        return mDatas;
    }

    /**
     * UI resticts only item can be drag and drop
     * @param from
     * @param to
     */
    public static void changeOrderForDataList(int from, int to) {
        Item item = (Item)mDatas.remove(from);
        if (to == 0)
            to = 1;
        mDatas.add(to, item);
        /* update category id if changed */
        //find previous category, the target category
        Category targetCategory = null;
        for (int i = to - 1; i >= 0; i--) {
            Object obj = mDatas.get(i);
            if (obj instanceof Category) {
                targetCategory = (Category) obj;
                break;
            }
        }
        item.categoryId = targetCategory.getId();//category may be default category, id = 0
        /* update display order */
        item.displayIndex = calculateItemDisplayOrder(to);
        item.dirty = true;
        
        ClientDBHelper.getInstance().dbInsertOrUpdateItem(item, new ArrayList<ItemParam>());
    }

    public static ArrayList<Category> getCategoryList() {
        if (mCategoryList == null)
            mCategoryList = new ArrayList<Category>();
        return mCategoryList;
    }

    public static Category getCategoryById(long id) {
        ArrayList<Category> list = getCategoryList();
        int size = list.size();
        for (int i = 0; i < size; i++) {
            Category category = list.get(i);
            if (category.getId() == id)
                return category;
        }
        return null;
    }

    public static void addCategory(Category category) {
        category.userId = CURRENT_USER.id;
        category.dirty = true;
        
        /* calculate display order */
        boolean hasUserCategory = false;
        String displayOrder = null;
        int size = getCategoryList().size();
        for (int i = 0; i < size; i++) {
            Category c = getCategoryList().get(i);
            if (c.getId() > 0) {
                hasUserCategory = true;
                break;
            }
        }
        if (hasUserCategory) {
            Category lastCategory = getCategoryList().get(size - 1);
            displayOrder = SortOrder.After(lastCategory.displayOrder);
        } else {
            displayOrder = SortOrder.Initial();
        }
        category.displayOrder = displayOrder;
        
        /* insert to database first */
        ClientDBHelper.getInstance().dbInsertOrUpdateModel(category);
        
        /* insert to memory list */
        getCategoryList().add(category);
        mDatas.add(category);
        
        sendBroadcastForDataChanged(DATA_CHANGED_CATEGORY_ITEM);
    }
    
    public static void changeOrderForCategory(int from, int to) {
        Category category = getCategoryList().remove(from);
        getCategoryList().add(to, category);
        Category prevCategory = null;
        Category nextCategory = null;
        int index = to - 1;
        if (index >= 0)
            prevCategory = getCategoryList().get(index);
        index = to + 1;
        if (index < getCategoryList().size())
            nextCategory = getCategoryList().get(index);
        category.displayOrder = SortOrder.Between(prevCategory == null ? null : prevCategory.displayOrder,
                nextCategory == null ? null : nextCategory.displayOrder);
        category.dirty = true;
        ClientDBHelper.getInstance().dbInsertOrUpdateModel(category);
    }
    
    public static void deleteCategory(Category category) {
        getCategoryList().remove(category);
        ClientDBHelper.getInstance().dbDeleteModel(category);
        sendBroadcastForDataChanged(DATA_CHANGED_CATEGORY_ITEM);
    }
    
    public static Item getItem(long id) {
        int size = mDatas.size();
        for (int i = 0; i < size; i++) {
            Object object = mDatas.get(i);
            if (object instanceof Item) {
                Item item = (Item) object;
                if (id == item.getId())
                    return item;
            }
        }
        return new Item();
    }

    public static void saveItem(Item item, ArrayList<ItemParam> deletedParams) {
        int originalPosition = -1;
        Item originalItem = null;
        boolean categoryChanged = false;
        int size = mDatas.size();
        if (item.getId() > 0) {
            for (int i = 0; i < size; i++) {
                Object object = mDatas.get(i);
                if (object instanceof Item) {
                    originalItem = (Item) object;
                    if (originalItem.getId() == item.getId()) {
                        originalPosition = i;
                        
                        if (originalItem.categoryId != item.categoryId) {
                            categoryChanged = true;
                        }
                        originalItem.copyFrom(item);
                        break;
                    }
                }
            }
            if (categoryChanged) {
                mDatas.remove(originalPosition);
                size = mDatas.size();
                item = originalItem;
            }
        }
        
        if (item.getId() == 0 || categoryChanged) {
            /* find insert position */
            int insert = size;
            for (int i = 0; i < size; i++) {
                Object object = mDatas.get(i);
                if (object instanceof Category) {
                    Category category = (Category) object;
                    if (category.getId() == item.categoryId) {
                        /*
                         * find the category of the item, insert item before next
                         * category item or end of the list
                         */
                        for (int j = i + 1; j < size; j++) {
                            object = mDatas.get(j);
                            if (object instanceof Category) {
                                insert = j;
                                break;
                            }
                        }
                        break;
                    }
                }
            }
            mDatas.add(insert, item);
            
            /* calculate new display order for item */
            item.displayIndex = calculateItemDisplayOrder(insert);
        }
        
        item.dirty = true;
        item.userId = CURRENT_USER.id;
        size = item.params.size();
        for (int i = 0; i < size; i++) {
            ItemParam param = item.params.get(i);
            param.userId = CURRENT_USER.id;
            if (param.getId() == 0)
                param.dirty = true;
        }
        ClientDBHelper.getInstance().dbInsertOrUpdateItem(item, deletedParams);
        
        sendBroadcastForDataChanged(DATA_CHANGED_CATEGORY_ITEM);
    }
    
    private static String calculateItemDisplayOrder(int itemIndex) {
        /* get previous, next Item if any */
        Item prevItem = null;
        Item nextItem = null;
        for (int i = itemIndex - 1; i >= 0; i--) {
            Object object = mDatas.get(i);
            if (object instanceof Item) {
                prevItem = (Item) object;
                break;
            }
        }
        int size = mDatas.size();
        for (int i = itemIndex + 1; i < size; i++) {
            Object object = mDatas.get(i);
            if (object instanceof Item) {
                nextItem = (Item) object;
                break;
            }
        }
        String displayOrder = null;
        if (prevItem == null && nextItem == null) {
            displayOrder = SortOrder.Initial();
        } else {
            displayOrder = SortOrder.Between(prevItem == null ? null : prevItem.displayIndex, nextItem == null ? null : nextItem.displayIndex);
        }
        return displayOrder;
    }

    public static void deleteItem(Item item) {
        sendBroadcastForStartLoadingData();
        mDatas.remove(item);
        ClientDBHelper.getInstance().dbDeleteModel(item);
        sendBroadcastForDataChanged(DATA_CHANGED_CATEGORY_ITEM);
    }
    
    public static void saveRecord(Record record, ArrayList<RecordParam> paramList) {
        record.userId = CURRENT_USER.id;
        record.dirty = true;
        int size = paramList.size();
        for (int i = 0; i < size; i++) {
            RecordParam param = paramList.get(i);
            param.userId = CURRENT_USER.id;
            param.dirty = true;
        }
        ClientDBHelper.getInstance().dbSaveRecord(record, paramList);
        sendBroadcastForDataChanged(DATA_CHANGED_RECORD);
    }
    
    public static void deleteRecord(Record record) {
        ClientDBHelper.getInstance().dbDeleteModel(record);
        sendBroadcastForDataChanged(DATA_CHANGED_RECORD);
    }

    private static void sendBroadcastForDataChanged(int dataType) {
        Intent intent = new Intent(ACTION_DATA_CHANGED);
        intent.putExtra(EXTRA_KEY_DATA_TYPE, dataType);
        Global.APP_CONTEXT.sendBroadcast(intent);
    }

    private static void sendBroadcastForStartLoadingData() {
        Intent intent = new Intent(ACTION_START_LOAD_DATA);
        Global.APP_CONTEXT.sendBroadcast(intent);
    }

    private static void sendBroadcastForLoadDataFailed() {
        Intent intent = new Intent(ACTION_LOAD_DATA_FAILED);
        Global.APP_CONTEXT.sendBroadcast(intent);
    }

    private static class LoadItemTask implements Runnable {

        @Override
        public void run() {
            LogUtil.info(TAG, "begin load data from local database");
            ArrayList<Category> categories = ClientDBHelper.getInstance().dbQueryAllCategory();

            ArrayList<Item> itemList = ClientDBHelper.getInstance().dbQueryAllItem();

            DataManager.setDatas(categories, itemList);
        }
    }

}
